var lastStepCount;
var programCounter;
var carryFlag;
var zeroFlag;
var theAccumulator;
var activeInstructions = new Array();
var programState = { READY: 0, RUN: 1, ERROR: 2 };
var runMode;
var fastMode;
var inputPort;
var outputPort;
var errorState;
var HIMEM;

// sensible default values
function init() {
	HIMEM = 100;
	lastStepCount = 0;
	programCounter = 0;
	carryFlag = 0;
	zeroFlag = 0;
	theAccumulator = 0;
	activeInstructions = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
	runMode = programState.READY;
	fastMode = 0;
	inputPort = 0;
	outputPort = 0;
	errorState = "READY";
}

// try to hide global variable behind getters and setters
function get_pc() {
	return(programCounter);
}

function get_HIMEM() {
	return(HIMEM);
}

function set_run_mode(mode) {
	runMode = mode;
}

function get_run_mode() {
	return(runMode);
}

function get_program_state() {
	return(Object.keys(programState)[runMode]);
}

function set_accumulator(value) {
	theAccumulator = value % 10;
}

function get_accumulator() {
	return(theAccumulator);
}

function adc_accumulator(value) {
	theAccumulator += value;
	theAccumulator += get_carry_flag();
	set_carry_flag(theAccumulator);
	theAccumulator = theAccumulator % 10;
	set_zero_flag(theAccumulator);
}

function set_zero_flag(testValue) {
	testValue = parseInt(testValue);
	if (0 == testValue) {
		zeroFlag = 1;
	} else {
		zeroFlag = 0;
	}
}

function get_zero_flag() {
	return(zeroFlag);
}

// not just the Accumulator, but also other tests
function update_zero_flag(testValue) {
	if (0 == testValue) {
		zeroFlag = 1;
	} else {
		zeroFlag = 0;
	}
}

// want to set the flag when the value is >10, but also
// want a nice clean set0/1 for load and store usage
function set_carry_flag(testValue) {
	testValue = parseInt(testValue);
	if (testValue > 9) {
		carryFlag = 1;
	} else {
		carryFlag = 0;
	}
}

function get_carry_flag() {
	return(carryFlag);
}

// create a mnemonic string for the given addr
function mnemonic_address(theAddr) {
	var opcode = activeInstructions[peek(theAddr)];
	var decode = instructionSet[opcode].mnemonic;
	if (2 == instructionSet[opcode].bytes) {
		decode += " " + peek(theAddr + 1);
	}
	if (3 == instructionSet[opcode].bytes) {
		var addr = parseInt(peek(theAddr + 2) * 10);
		addr += parseInt(peek(theAddr + 1));
		decode += " " + addr;
	}
	return(decode);
}

// this is dodgy, but hard to abstract the reading of the memory locations
// away from the UI and CSS id's unless we back the whole memory with an
// array or other data store, and then copy to/from the array before starting
// any processing - I like the idea of reading the DOM live for the program
// execution as it feels closer to the original Peeko-Computer intention
function peek(address) {
	var loc = address;
	if (loc < 10)
		loc = "0" + loc;
	return parseInt($('#' + loc).text());
}

// I/O is via separate functions, so this is a simple write
function poke(address, value) {
	var loc = address;
	if (loc < 10)
		loc = "0" + loc;
	$('#' + loc).text(value);
}

function get_port(port) {
}

function set_port(port, value) {
	// should never be able to get outside this range, but...
	if ((port >= 0) & (port <= 9)) {
		var outputDisplay = parseInt($('#100').text());
		var currentLine = $('#outputdisplay').text();
		if (0 == port) {
			// what do we do with the current output display ?
			switch(outputDisplay) {
			// roll left immediate
			case 4:
				var overflow = currentLine.substring(0, 1);
				currentLine = currentLine.substring(1, 8) + overflow;
				$('#outputdisplay').text(currentLine);
				break;
			// roll right immediate
			case 5:
				var overflow = currentLine.substring(7, 8);
				currentLine = overflow + currentLine.substring(0, 7);
				$('#outputdisplay').text(currentLine);
				break;
			}
		}
		if (1 == port) {
			// this location is RAZ, so undo anything that might have got in
			$('#101').text(0);
			// what do we do with the current output display ?
			switch(outputDisplay) {
			// clear to zero and overwrite
			case 0:
				$('#outputdisplay').text('0000000' + value);
				break;
			// overwrite
			case 1:
				currentLine = currentLine.substring(0, 7);
				$('#outputdisplay').text(currentLine + value);
				break;
			// shift left and write
			case 2:
				currentLine = currentLine.substring(1, 8);
				$('#outputdisplay').text(currentLine + value);
				break;
			// shift right and write
			case 3:
				currentLine = "0" + currentLine.substring(0, 6);
				$('#outputdisplay').text(currentLine + value);
				break;
			}
		}
	}
	if ((port >= 2) && (port <= 9)) {
	        var led = 7 - (9 - port);
	        switch(value) {
	        case 0:
	    	    $('#led' + led).css('background-color', 'red');
	    	    break;
	        case 1:
	    	    $('#led' + led).css('background-color', 'lawngreen');
	    	    break;
	        case 2:
	    	    $('#led' + led).css('background-color', 'yellow');
	    	    break;
	        case 3:
	    	    $('#led' + led).css('background-color', 'blue');
	    	    break;
	        case 4:
	    	    $('#led' + led).css('background-color', 'magenta');
	    	    break;
	        case 5:
	    	    $('#led' + led).css('background-color', 'cyan');
	    	    break;
	        case 6:
	    	    $('#led' + led).css('background-color', 'white');
	    	    break;
	        case 7:
	    	    $('#led' + led).css('background-color', 'lightgrey');
	    	    break;
	        case 8:
	    	    $('#led' + led).css('background-color', 'black');
	    	    break;
	        case 9:
	    	    $('#led' + led).css('background-color', 'mediumorchid');
	    	    break;
	        }
	}
}

function halt_program(state) {
	set_run_mode(state);
	update_status();
}
function decode_pc() {
	var opcode = activeInstructions[peek(get_pc())];
	var decode = instructionSet[opcode].mnemonic;
	if (2 == instructionSet[opcode].bytes) {
		decode += " " + peek(get_pc() + 1);
	}
	if (3 == instructionSet[opcode].bytes) {
		var addr = parseInt(peek(get_pc() + 2) * 10);
		addr += parseInt(peek(get_pc() + 1));
		decode += " " + addr;
	}
	$('#mnemonic').text(decode);
}
function show_pc() {
	var cellID;
	if (get_pc() < 10) {
		cellID = "#0" + get_pc();
	} else {
		cellID = "#" + get_pc();
	}
	$('.peekotable').find('td').removeClass('highlight');
	$(cellID).addClass('highlight');
}
function show_accumulator() {
	$('#accumulator').text(theAccumulator);
}
function show_carry() {
	$('#carry').text(get_carry_flag());
}
function show_zero() {
	$('#zero').text(get_zero_flag());
}
function show_steps() {
	$('#steps').text("STEPS: " + lastStepCount);
}
function show_activity() {
	if (programState.RUN != runMode) {
		$('#activity').text(errorState);
	} else {
		$('#activity').text("RUN");
	}
}
function update_status() {
	show_steps();
	show_activity();
	show_accumulator();
	show_carry();
	show_zero();
	decode_pc();
}

function get_instruction_set(html) {
	var fullSet, pre, post;
	if (html) {
		fullSet = '<ol start="0" class="instructionlist">';
		pre = "<li>";
		post = "</li>";
	} else {
		fullSet = "";
		pre = "";
		post = "\n";
	}
	for (var i in activeInstructions) {
		fullSet += pre + instructionSet[activeInstructions[i]].mnemonic + post ;
	}
	if (html) {
		fullSet += "</ol>";
	}
	return(fullSet);
}

function get_all_instructions(html) {
	var fullSet, pre, post;
	if (html) {
		fullSet = '<ol start="0" class="instructionset">';
		pre = "<li>";
		post = "</li>";
	} else {
		fullSet = "";
		pre = "";
		post = "\n";
	}
	for (var i in instructionSet) {
		fullSet += pre + instructionSet[i].mnemonic + post;
	}
	if (html) {
		fullSet += "</ol>";
	}
	return(fullSet);
}

function change_instruction(s, d) {
	if ((s != null) && (d != null)) {
		activeInstructions[s] = d;
	}
}

function set_pc(pc) {
	pc = parseInt(pc);
	if (pc >= get_HIMEM()) {
		halt_program(programState.ERROR);
		pc = 0;
	}
	programCounter = pc % get_HIMEM();
}

function change_pc(diff) {
	programCounter += diff;
	if (programCounter < 0)
		programCounter += get_HIMEM();
	programCounter = programCounter % get_HIMEM();
}

function execute_pc() {
	var opcode;

	if (programCounter >= get_HIMEM()) {
		set_pc(0);
		return;
	}

	opcode = activeInstructions[peek(get_pc())];
	change_pc(window["execute_" + instructionSet[opcode].mnemonic](instructionSet[opcode].bytes));
	lastStepCount += instructionSet[opcode].bytes;
}

function save_state(title, description) {
	var mem = new Array();
	for (var hi=0; hi < 8; hi++) {
		for (var lo=0; lo<10; lo++) {
			mem.push($('#'+hi+lo).text());
		}
	}
	var theState = { "title": title,
			"description": description,
			"activeInstructions": JSON.stringify(activeInstructions),
			"memory": JSON.stringify(mem)
		};
	var allCode = JSON.parse(localStorage.getItem('peekoComputer'));
	if (null == allCode) {
		allCode = new Array();
	}
	var replaced = false;
	for (var i in allCode) {
		if (allCode[i].title == title) {
			allCode[i] = theState;
			replaced = true;
		}
	}
	if (false == replaced) {
		allCode.push(theState);
	}
	localStorage.setItem('peekoComputer', JSON.stringify(allCode));
}

function load_state() {
	halt_program(programState.READY);
	activeInstructions = JSON.parse(localStorage.getItem('activeInstructions'));
	var mem = JSON.parse(localStorage.getItem('memory'));
	for (var hi=0; hi < 8; hi++) {
		for (var lo=0; lo<10; lo++) {
			$('#'+hi+lo).text(mem.shift());
		}
	}
	$('#instset').html(get_instruction_set(true));
	set_pc(0);
	update_status();
	show_pc();
}
